﻿using System;
using System.Text;
using IOP.Extension.Convert;
using System.Runtime.CompilerServices;

namespace IOP.Protocols.MQTT.Extension
{
    /// <summary>
    /// MQTT包扩展
    /// </summary>
    public static class MQTTPackageExtensions
    {
        /// <summary>
        /// 包最大长度
        /// </summary>
        const uint PACKAGEMAX = 268_435_455;

        /// <summary>
        /// 字符串转MQTTUTF8字节数组
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Span<byte> SrtingToMqttUTF8Bytes(this string data)
        {
            int index = 0;
            var bytes = Encoding.UTF8.GetBytes(data).AsSpan();
            if (bytes.Length > ushort.MaxValue) throw new ArgumentOutOfRangeException(nameof(data), "this string is out of 65535 bytes");
            var result = new Span<byte>(new byte[sizeof(ushort) + bytes.Length]);
            ((ushort)(bytes.Length)).WriteBytes(ref result, ref index);
            bytes.CopyTo(result[index..result.Length]);
            return result;
        }

        /// <summary>
        /// 字符串转MQTTUTF8字节数组
        /// </summary>
        /// <param name="data"></param>
        /// <param name="target"></param>
        /// <param name="index"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void SrtingToMqttUTF8Bytes(this string data, ref Span<byte> target, ref int index)
        {
            var bytes = Encoding.UTF8.GetBytes(data.AsSpan(), target[(index + sizeof(ushort))..target.Length]);
            if (bytes > ushort.MaxValue) throw new ArgumentOutOfRangeException(nameof(data), "this string is out of 65535 bytes");
            ((ushort)bytes).WriteBytes(ref target, ref index);
            index += bytes;
        }

        /// <summary>
        /// 获取剩余长度
        /// </summary>
        /// <param name="length"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static Span<byte> GetRemainLengthBytes(this uint length)
        {
            if (length > PACKAGEMAX) throw new ArgumentOutOfRangeException(nameof(length), "package length is out of max value");
            Span<byte> local = stackalloc byte[sizeof(uint)];
            int index = 0;
            do
            {
                var oneByte = length % 128;
                length /= 128;
                if (length > 0)
                    oneByte |= 0x80;
                local[index++] = (byte)oneByte;
            }
            while (length > 0);
            var result = new Span<byte>(new byte[index]);
            local.Slice(0, index).CopyTo(result);
            return result;
        }

        /// <summary>
        /// 获取剩余长度
        /// </summary>
        /// <param name="remainLength"></param>
        /// <param name="target"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void WriteRemainLengthToBytes(this uint remainLength, ref Span<byte> target, ref int index)
        {
            if (remainLength > PACKAGEMAX) throw new ArgumentOutOfRangeException(nameof(remainLength), "package length is out of max value");
            do
            {
                var oneByte = remainLength % 128;
                remainLength /= 128;
                if (remainLength > 0)
                    oneByte |= 0x80;
                target[index++] = (byte)oneByte;
            }
            while (remainLength > 0);
        }

        /// <summary>
        /// 获取剩余长度在数据序列中占用的真正大小
        /// </summary>
        /// <param name="remainLength"></param>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int GetRemainBytesLength(this uint remainLength)
        {
            if (remainLength > PACKAGEMAX) throw new ArgumentOutOfRangeException(nameof(remainLength), "package length is out of max value");
            uint length = remainLength;
            int bytesLength = 0;
            uint oneByte = 0;
            do
            {
                oneByte = length % 128;
                length /= 128;
                if (length > 0)
                    oneByte |= 0x80;
                bytesLength++;
            }
            while (length > 0);
            return bytesLength;
        }
    }
}
